/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tal.drivechain.chain.shell;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import tal.drivechain.chain.Chain;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.chain.script.ScriptCommand;
import tal.drivechain.chain.NodeListener;
import tal.drivechain.parsing.Tokenizer;
import tal.drivechain.plugin.Channel;
import tal.drivechain.system.Globals;
import tal.drivechain.variable.Variable;

/**
 *
 * @author eisental
 */
public class DriveShell {
    static final String SHORTCUTS_PATH = System.getProperty("user.dir") + "/shortcuts";

    private Chain activeChain;
    private HashMap<String, Spy> spies = new HashMap<String, Spy>();
    private boolean isRecording = false;
    private List<ScriptCommand> recording;
    private NodeListener activeChainListener;
    private Properties shortcuts;

    private PrintStream out;
    private ShellCommand[] commandHandlers = new ShellCommand[] {
        new RunCommand(this),
        new EvaluateCommand(this),
        new AboutCommand(this),
        new CopyPasteCommand(this),
        new ActiveChainObject(this),
        new NodeObject(this),
        new ChainObject(this),
        new ModuleObject(this),
        new NodeExportObject(this),
        new MetaDataObject(this),
        new PropertyObject(this),
        new VariableObject(this),
        new ShortcutObject(this),
        new PatchCommand(this),
        new SpyObject(this),
        new SendCommand(this),
        new MapCommand(this),
        new GarbageCollectorCommand(this),
        new RestartCommand(this),
        new ShutdownCommand(this),
        new LogObject(this, true),
        new LogObject(this, false),
        new ShellScriptObject(this),
    };

    public DriveShell(PrintStream out) {
        shortcuts = new Properties();

        try {
            shortcuts.load(new FileInputStream(new File(SHORTCUTS_PATH)));
        } catch (IOException ex) {
            out.println("Error while reading shell shortcuts file:\n\t" + ex);
        }

        this.out = out;
        for (ShellCommand c : commandHandlers)
            c.setPrintStream(out);
        
        greet();

    }

    public void greet() {
        out.println();
        String greet = ("driveshell running on " + Globals.VERSION.toString(true, true, true, true));
        String line = "";
        for (int i=0; i<greet.length(); i++)
            line += "-";
        out.println(line);
        out.println(greet);
        out.println(line);
    }

    public HashMap<String, Spy> getSpies() { return spies; }

    public Chain getActiveChain() {
        return activeChain;
    }

    public ShellCommand[] getShellCommands() { return commandHandlers; }

    public PrintStream getPrintStream() {
        return out;
    }

    public void setActiveChain(Chain currentChain) {
        if (this.activeChain!=null)
            this.activeChain.removeNodeListener(activeChainListener);
        this.activeChain = currentChain;
        activeChainListener = new NodeListener() {
            public void messageReceived(ChainNode src, Variable var, Channel channel, long timestamp) {            }
            public void messageSent(ChainNode src, Variable var, long timestamp) {            }
            public void nameChanged(ChainNode src) {           }
            public void metaChanged(Chain src) {            }
            public void connectionsChanged(ChainNode src) {            }

            public void nodeDestroyed(ChainNode src) {
                if (activeChain.getParentChain()!=null)
                    setActiveChain(activeChain.getParentChain());
                else
                    setActiveChain(Globals.getRootChain());
            }
            
            public void nodeMoved(ChainNode src) {            }
            public void propertyChanged(ChainNode src, String name, Object value) {            }
            public void metaChanged(ChainNode src) {            }
        };
        activeChain.addNodeListener(activeChainListener);
        out.println(" --> " + currentChain.getPath());
    }

    public static ScriptCommand[] parseCommand(String str) {
        String[] commands = new Tokenizer(str, ';').getTokens();
        ScriptCommand[] scs = new ScriptCommand[commands.length];
        for (int i=0; i<scs.length; i++)
            scs[i] = ScriptCommand.parseScriptCommand(commands[i]);
        return scs;
    }

    public void runCommand(ScriptCommand command) {
        if (isRecording) recording.add(command);

        if (shortcuts.containsKey(command.getName())) {
            runShortcut(command);
            return;
        }

        for (ShellCommand c : commandHandlers)
            if (c.runCommand(command)) return;

        throw new IllegalArgumentException("Unknown shell command: " + command);
    }


    public void recordCommands() {
        isRecording = true;
        recording = new ArrayList<ScriptCommand>();
    }

    public String getRecordedScript() {
        if (recording==null) return "";
        String ret = "";
        for (int i=0; i<recording.size()-1; i++) {
            ret += recording.get(i).toString().trim() + ";\n";
        }
        recording.clear(); 
        return ret.substring(0, ret.length());
    }

    void runShellScript(String script) {
        ScriptCommand[] commands = parseCommand(script);

        boolean recStat = isRecording;
        isRecording = false;
        
        for (ScriptCommand command : commands) 
                this.runCommand(command);
        
        
        if (recStat)
            isRecording = true;

    }

    private void runShortcut(ScriptCommand command) {
        String t = shortcuts.getProperty(command.getName()).replace("%1", command.getArg1())
                .replace("%2", command.getArg2())
                .replace("%%", command.getArg1() + " " + command.getArg2());

        ScriptCommand[] commands = parseCommand(t);
        for (ScriptCommand c : commands) {
            out.println(">> " + c.toString() + "\n");
            for (ShellCommand sc : commandHandlers)
                if (sc.runCommand(c)) break;
        }
    }

    public Properties getShortcuts() { return shortcuts; }
}
